Skip to content

fix(esp32): use runtime node_id from NVS in outgoing packets - #232

Closed
melodykke wants to merge 2 commits into
ruvnet:mainfrom
melodykke:fix/runtime-node-id
Closed

fix(esp32): use runtime node_id from NVS in outgoing packets#232
melodykke wants to merge 2 commits into
ruvnet:mainfrom
melodykke:fix/runtime-node-id

Conversation

@melodykke

Copy link
Copy Markdown

Summary

Fix an ESP32 firmware issue where outgoing packets could use the compile-time node ID instead of the runtime node ID loaded from NVS.

In multi-node setups, this could cause a provisioned board to still appear with the wrong node_id on the receiver side.

Root Cause

Some packet serialization paths were still using CONFIG_CSI_NODE_ID instead of g_nvs_config.node_id.

That meant NVS provisioning could succeed while transmitted packets still carried the compile-time default.

Changes

Use g_nvs_config.node_id consistently in outgoing packet paths.

Validation

Tested with a real ESP32-S3 setup.

  • provisioned a board with node_id=2 via NVS
  • rebuilt and reflashed firmware
  • confirmed RuView then reported node_id=2 correctly

Impact

This makes runtime node provisioning behave correctly for multi-node deployments.

- 为 Rust sensing server 增加空间布局配置入口,支持加载节点位置与语义区域定义,并扩展空间融合解释输出。

- 在 Docker 镜像中打包 config 目录,确保部署后可直接读取 spatial-layout.json。

- 修正 ESP32 显示界面中的节点编号来源,改为使用运行时 NVS 配置而非编译期常量。

@ruvnet ruvnet left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — PR #232

Recommendation: HOLD (partially superseded, scope creep)

Summary

This PR from @melodykke has two distinct parts:

Part 1 — Firmware: NVS runtime node_id (the stated purpose)
Replaces CONFIG_CSI_NODE_ID with g_nvs_config.node_id across 4 firmware files:

  • csi_collector.c — packet serialization
  • display_ui.c — UI node label
  • edge_processing.c — compressed frame and vitals packets
  • wasm_runtime.c — WASM output packets

Part 2 — Sensing server: Multi-node spatial fusion (~800 lines, unstated)
Adds an entire spatial fusion system to the sensing server:

  • SpatialLayoutConfig with JSON node positions and zone definitions
  • Per-node Esp32NodeState with independent signal processing pipelines
  • Zone-based presence/motion/vital scoring with cross-node consensus
  • FusionConsensus and FusionExplanation structs
  • config/spatial-layout.json spatial layout file
  • Docker config update

Security Assessment

  • No security vulnerabilities found. The extern nvs_config_t g_nvs_config pattern is safe — it is initialized before use in app_main.
  • Path concern: spatial-layout.json is loaded from disk via --spatial-config flag — no user-facing path traversal risk since it is a CLI argument, not a web endpoint.
  • The adaptive_override refactor correctly decouples from AppStateInner, improving testability.

Conflict Assessment (ADR-069 through ADR-078)

  • Part 1 (NVS node_id) is already on main. Commit 8a84748a8 ("fix(firmware): use NVS node_id instead of Kconfig constant") already made these exact same changes. Merging would conflict.
  • Part 2 (spatial fusion) will conflict heavily with sensing-server/src/main.rs on main, which has had significant changes through ADR-069 (Cognitum pipeline), ADR-070+, and the recent v0.5.3 cross-node fusion work.

Code Quality Notes (Part 2)

  • Good: Per-node state isolation prevents cross-contamination of temporal features between ESP32 nodes.
  • Good: Zone scoring with geometric weighting is a sound approach.
  • Concern: ~800 lines of new code added to an already large main.rs (3000+ lines). This should be extracted into separate modules (spatial.rs, fusion.rs).
  • Concern: The fuse_esp32_update function is doing too much — it combines filtering, scoring, consensus, and explanation generation. Should be decomposed.

Recommendation

Part 1 is already merged. Part 2 introduces valuable multi-node fusion but:

  1. Conflicts extensively with current main
  2. Needs to be rebased onto current main
  3. Should be split into a separate PR with proper module decomposition
  4. Should be reviewed against our existing cross-node fusion work (v0.5.3, ADR-069)

Verdict: HOLD — Ask contributor to rebase and split the sensing-server changes into a separate PR.

@ruvnet

ruvnet commented Apr 3, 2026

Copy link
Copy Markdown
Owner

Thanks @melodykke — the NVS node_id fix is already on main (8a84748). The multi-node spatial fusion concept is interesting but conflicts extensively with v0.5.3-v0.5.5 changes (cross-node fusion, ADR-069 Seed pipeline, ADR-075 MinCut person counting). Could you rebase and split the spatial fusion into a separate PR targeting the sensing-server only? Happy to review a rebased version.

ruvnet added a commit that referenced this pull request Apr 15, 2026
Users on multi-node ESP32 deployments have been reporting for months
that their provisioned `node_id` reverts to the Kconfig default of `1`
in UDP frames and the `csi_collector` init log, despite boot showing:

    nvs_config: NVS override: node_id=4
    main: ESP32-S3 CSI Node (ADR-018) - Node ID: 4
    csi_collector: CSI collection initialized (node_id=1, channel=11)

See #232, #375, #385, #386, #390. The root memory-corruption path for
the `g_nvs_config.node_id` byte has not been definitively isolated
(does not reproduce on my attached ESP32-S3 running current source
and the v0.6.0 release binary), but the UDP frame header can be made
tamper-proof regardless:

1. `csi_collector_init()` now captures `g_nvs_config.node_id` into a
   module-local `static uint8_t s_node_id` at init time.
2. `csi_serialize_frame()` reads `buf[4]` from `s_node_id`, not from
   the global - so any later corruption of `g_nvs_config` cannot
   affect outgoing CSI frames.
3. All other consumers (`edge_processing.c` x3, `wasm_runtime.c`,
   `display_ui.c`, `main.c swarm_bridge_init`) now go through a new
   `csi_collector_get_node_id()` accessor instead of reading the
   global directly.
4. A canary at end-of-init logs `WARN` if `g_nvs_config.node_id`
   already diverges from the captured value - this will pinpoint
   the corruption path if it happens on a user's device.

Hardware validation on attached ESP32-S3 (COM8):
  - NVS loads node_id=2
  - Boot log: `main: ... Node ID: 2`
  - NEW log: `csi_collector: Captured node_id=2 at init (defensive
    copy for #232/#375/#385/#390)`
  - Init log: `csi_collector: CSI collection initialized (node_id=2)`
  - UDP frame byte[4] = 2 (verified via socket sniffer, 15/15 packets)

This is defense in depth - it shields the UDP frame from whatever
upstream bug is clobbering the struct. When a user hits the original
bug, the canary WARN will help isolate the root cause.

Refs #232 #375 #385 #386 #390

Co-Authored-By: claude-flow <ruv@ruv.net>
proffesor-for-testing added a commit to proffesor-for-testing/RuView that referenced this pull request Apr 16, 2026
The CSI callback reads g_nvs_config.filter_mac_set and filter_mac on
every invocation (100-500 Hz). If wifi_init_sta() corrupts g_nvs_config
(same root cause as the node_id clobber), the callback reads garbage
from the struct, leading to Core 0 LoadProhibited panic after ~2400
callbacks (~70 seconds of operation).

Extends the early-capture pattern from the node_id fix to also copy
filter_mac_set and filter_mac into module-local statics before WiFi
init runs. Adds canary logging to detect filter_mac corruption.

Observed on device 80:b5:4e:c1:be:b8 via serial:
  CSI cb #2400 → Guru Meditation Error: Core 0 panic'ed (LoadProhibited)
  → TG0WDT_SYS_RST → reboot → crash again at ~2900 callbacks

Refs ruvnet#232 ruvnet#375 ruvnet#385 ruvnet#386 ruvnet#390

Co-Authored-By: Ruflo & AQE
AntwerpDesignsIonity pushed a commit to AntwerpDesignsIonity/AEDI-SIGHT-IO-with-RuView that referenced this pull request Apr 19, 2026
…vnet#390)

Users on multi-node ESP32 deployments have been reporting for months
that their provisioned `node_id` reverts to the Kconfig default of `1`
in UDP frames and the `csi_collector` init log, despite boot showing:

    nvs_config: NVS override: node_id=4
    main: ESP32-S3 CSI Node (ADR-018) - Node ID: 4
    csi_collector: CSI collection initialized (node_id=1, channel=11)

See ruvnet#232, ruvnet#375, ruvnet#385, ruvnet#386, ruvnet#390. The root memory-corruption path for
the `g_nvs_config.node_id` byte has not been definitively isolated
(does not reproduce on my attached ESP32-S3 running current source
and the v0.6.0 release binary), but the UDP frame header can be made
tamper-proof regardless:

1. `csi_collector_init()` now captures `g_nvs_config.node_id` into a
   module-local `static uint8_t s_node_id` at init time.
2. `csi_serialize_frame()` reads `buf[4]` from `s_node_id`, not from
   the global - so any later corruption of `g_nvs_config` cannot
   affect outgoing CSI frames.
3. All other consumers (`edge_processing.c` x3, `wasm_runtime.c`,
   `display_ui.c`, `main.c swarm_bridge_init`) now go through a new
   `csi_collector_get_node_id()` accessor instead of reading the
   global directly.
4. A canary at end-of-init logs `WARN` if `g_nvs_config.node_id`
   already diverges from the captured value - this will pinpoint
   the corruption path if it happens on a user's device.

Hardware validation on attached ESP32-S3 (COM8):
  - NVS loads node_id=2
  - Boot log: `main: ... Node ID: 2`
  - NEW log: `csi_collector: Captured node_id=2 at init (defensive
    copy for ruvnet#232/ruvnet#375/ruvnet#385/ruvnet#390)`
  - Init log: `csi_collector: CSI collection initialized (node_id=2)`
  - UDP frame byte[4] = 2 (verified via socket sniffer, 15/15 packets)

This is defense in depth - it shields the UDP frame from whatever
upstream bug is clobbering the struct. When a user hits the original
bug, the canary WARN will help isolate the root cause.

Refs ruvnet#232 ruvnet#375 ruvnet#385 ruvnet#386 ruvnet#390

Co-Authored-By: claude-flow <ruv@ruv.net>
ruvnet added a commit that referenced this pull request Apr 28, 2026
…ies + esptool v5 (rebased #397)

* fix(firmware): move defensive node_id capture before wifi_init_sta()

The original defensive copy in csi_collector_init() (line 172 of main.c)
runs AFTER wifi_init_sta() (line 147), which on some ESP32-S3 devices
corrupts g_nvs_config.node_id back to the Kconfig default of 1.

Reproduced on device 80:b5:4e:c1:be:b8 (ESP32-S3 QFN56 rev v0.2):
  - NVS provisioned with node_id=5
  - Release firmware (no fix): seed receives node_id=1 (clobbered)
  - This patch: seed receives node_id=5 (correct)

Changes:
  - Add csi_collector_set_node_id() called from main.c immediately
    after nvs_config_load(), before wifi_init_sta() runs
  - csi_collector_init() now detects and logs the clobber if early
    capture disagrees with current g_nvs_config value
  - Fallback path preserved: if set_node_id() is never called,
    init() still captures from g_nvs_config (backwards compatible)

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(firmware): defensive copy of filter_mac to prevent callback crash

The CSI callback reads g_nvs_config.filter_mac_set and filter_mac on
every invocation (100-500 Hz). If wifi_init_sta() corrupts g_nvs_config
(same root cause as the node_id clobber), the callback reads garbage
from the struct, leading to Core 0 LoadProhibited panic after ~2400
callbacks (~70 seconds of operation).

Extends the early-capture pattern from the node_id fix to also copy
filter_mac_set and filter_mac into module-local statics before WiFi
init runs. Adds canary logging to detect filter_mac corruption.

Observed on device 80:b5:4e:c1:be:b8 via serial:
  CSI cb #2400 → Guru Meditation Error: Core 0 panic'ed (LoadProhibited)
  → TG0WDT_SYS_RST → reboot → crash again at ~2900 callbacks

Refs #232 #375 #385 #386 #390

Co-Authored-By: Ruflo & AQE

* fix(firmware): MGMT-only promiscuous filter to prevent SPI cache crash

The WiFi driver's wDev_ProcessFiq interrupt handler crashes with
LoadProhibited in cache_ll_l1_resume_icache when promiscuous mode
captures MGMT+DATA frames (100-500 interrupts/sec). The high interrupt
rate races with SPI flash cache operations, corrupting cache state.

Changes:
- Promiscuous filter: MGMT+DATA → MGMT-only (~10 Hz beacons)
- CSI config: disable htltf_en and stbc_htltf2_en (LLTF-only)

LLTF provides 64 subcarriers (HT20) — sufficient for presence,
breathing, and fall detection. The 10 Hz beacon rate eliminates
the SPI flash cache contention that caused the crash.

Verified on device 80:b5:4e:c1:be:b8:
- Before: LoadProhibited crash at ~1600-2400 callbacks (every ~70s)
- After: 2700+ callbacks over 4.7 minutes, zero crashes

Backtrace decode confirmed crash in ESP-IDF closed-source WiFi blob:
  _xt_lowint1 → wDev_ProcessFiq → spi_flash_restore_cache
  → cache_ll_l1_resume_icache → EXCVADDR=0x00000004 (NULL deref)

Co-Authored-By: Ruflo & AQE

* fix(provision): write-flash → write_flash for esptool v5 compat

esptool v5+ rejects hyphenated subcommands. The provision script
used 'write-flash' which fails with "invalid choice". Changed to
'write_flash' (underscore) which works with both old and new esptool.

Co-Authored-By: Ruflo & AQE

* fix(firmware): 50 Hz callback rate gate + sdkconfig extra IRAM opt

- Add early rate gate in wifi_csi_callback at 50 Hz (defense-in-depth,
  does not prevent crash alone but reduces callback execution time)
- Add null-data injection timer infrastructure (disabled — TX adds
  interrupt pressure that triggers the SPI cache crash, RuView#396)
- sdkconfig.defaults: add CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y
- sdkconfig.defaults: document SPIRAM XIP attempt (crashes differently)

Co-Authored-By: Ruflo & AQE

* fix(firmware): address PR #397 review feedback

Applies @ruvnet's five review requests on PR #397 (RuView#397 comment
4289417527):

1. **Inline comment on `provision.py` `write_flash`** — ESP-IDF v5.4
   bundles esptool 4.10.0 (underscore-only). #391's hyphen swap broke
   the documented venv flow; kept the underscore form and added a
   three-line comment warning future maintainers not to "re-fix" it.

2. **Correct `edge_processing.c` sample_rate** (blocking) — changed
   hard-coded `20.0f` → `10.0f` at line 718 so
   `estimate_bpm_zero_crossing()` matches the MGMT-only CSI rate.
   Without this, breathing and heart-rate reports were 2× the true
   value. Added a comment tying the constant to the callback rate gate.

3. **Removed disabled probe-injection infrastructure** — dropped the
   forward declaration, the `CSI_PROBE_INTERVAL_MS` define, six static
   variables (`s_probe_timer`, `s_probe_tx_count`, `s_probe_tx_fail`,
   `s_ap_bssid`, `s_ap_bssid_known`), and three functions
   (`csi_send_probe_request`, `probe_timer_cb`,
   `csi_collector_start_probe_timer`). None were reachable.
   `csi_inject_ndp_frame()` reverted to the original ADR-029 stub.
   Can be revived from this commit's parent if needed.

4. **Cleaned `sdkconfig.defaults`** — removed the SPIRAM prose and
   commented-out `# CONFIG_SPIRAM is not set` line. Kept only the live
   `CONFIG_ESP_WIFI_EXTRA_IRAM_OPT=y` with a concise rationale.

5. **Bumped firmware version 0.6.1 → 0.6.2** and added four
   `[Unreleased]` CHANGELOG entries covering the SPI cache crash fix,
   the `filter_mac` / `node_id` clobber defense, the sample-rate
   correction, and the `write_flash` command-form revert.

Net: +39 / -128 across six files.

Validation in this devcontainer:
- Static sanity on modified C files: braces balance (csi_collector.c
  59/59; edge_processing.c 96/96), zero dangling references to removed
  probe-injection symbols.
- Rust workspace tests and Python proof not executed here — cargo not
  installed and pip blocked by PEP 668. Deferring hardware build +
  flash + miniterm verification to @ruvnet's COM7 per his offer in
  the review comment.

Co-Authored-By: claude-flow <ruv@ruv.net>

---------

Co-authored-by: Dragan Spiridonov <spiridonovdragan@gmail.com>
b1n4ryVisuals pushed a commit to b1n4ryVisuals/RuView that referenced this pull request Apr 29, 2026
- Add v1/src/integrations/td_osc_bridge.py: WebSocket→OSC bridge that
  forwards person count, presence, x/y/z positions, vitals, node health,
  and signal features to TouchDesigner via UDP OSC. Converts 640×480
  pixel canvas to configurable room metres. Stable slot assignment
  (1..max_persons) prevents unbounded person-ID growth in TD channels.

- Add ui/calibrate.html: mobile-friendly calibration page served at
  /ui/calibrate.html. Start/Stop buttons with live frame counter polling.
  Allows recalibration without the "fresh calibration exists" block.

- Fix calibration always capturing 0 frames (two root causes):
  1. FieldModel default n_subcarriers=56 mismatched ESP32-S3's 114
     subcarriers, causing silent DimensionMismatch on every feed call.
     maybe_feed_calibration now lazily recreates the model on the first
     frame with the actual subcarrier count.
  2. min_calibration_frames=12000 (10 min) made finalize_calibration
     always return InsufficientCalibration. Lowered to 200 (~10 s).

- Add FieldModel::n_subcarriers() accessor for the lazy-init check.

- firmware: read node_id directly from NVS at csi_collector_init() to
  avoid g_nvs_config corruption by WiFi stack init (ruvnet#232/ruvnet#375/ruvnet#385/ruvnet#390).

Co-Authored-By: claude-flow <ruv@ruv.net>
pull Bot pushed a commit to esinanturan/wifi-densepose that referenced this pull request May 11, 2026
Adds a fast per-PR gate that asserts previously-shipped fixes are still
present in the tree — the CI analogue of the ruflo witness fix-marker
system, but self-contained (no plugin dependency, reviewable as plain
JSON). Complements the heavier checks (firmware build, deterministic
pipeline proof, release witness bundle) by catching the silent-revert
class of regression that build+test wouldn't.

  - scripts/fix-markers.json   manifest: 11 markers (RuView#396, ruvnet#521,
    ruvnet#517, ruvnet#505, ruvnet#354, ruvnet#263, ruvnet#266/ruvnet#321, ruvnet#265, ruvnet#232/ruvnet#375/ruvnet#385/ruvnet#386/ruvnet#390,
    ADR-028 proof + witness bundle). Each has files / require (literal
    substring or /regex/) / optional forbid / rationale / ref.
  - scripts/check_fix_markers.py  stdlib-only checker. Exit 0 clean /
    1 regression / 2 bad manifest. Modes: --list, --json, --only ID.
  - .github/workflows/fix-regression-guard.yml  runs on PR + push to
    main/master; gates on the checker and writes the result table into
    the run summary + an artifact.

If a fix is intentionally removed, update scripts/fix-markers.json in the
same PR with a rationale — the diff becomes the audit trail.

Co-Authored-By: claude-flow <ruv@ruv.net>
Alephant6 added a commit to Alephant6/RuView that referenced this pull request May 16, 2026
…d + 2 months of fixes)

The pre-built ESP32-S3 binaries in release_bins/ were built from commit
66e2fa0 (v0.4.3.1-esp32, compiled 2026-03-15) and are now ~2 months
stale. In that window upstream landed several user-visible fixes,
including PR ruvnet#390 "defensive node_id capture prevents runtime clobber",
which we hit on real hardware while bringing up a 3-node deployment.

## The bug users hit with the stale binaries

Symptom: provision with `--node-id 2` (or 3, etc.), boot log says
`Node ID: 2`, but a few hundred ms later csi_collector reports
`node_id=1` and every UDP packet goes out with node_id=1. The MQTT
bridge then registers all nodes as Node 1 in Home Assistant, so a
multi-node deployment looks like a single duplicated node.

Root cause (per ruvnet#232/ruvnet#375/ruvnet#390): the WiFi driver init can corrupt the
g_nvs_config struct on some chips, reverting node_id to its Kconfig
default of 1. The fix is for main.c to copy node_id to a module-local
static (`csi_collector_set_node_id`) BEFORE wifi_init_sta() runs. The
fix is in source but never made it into release_bins/.

## Verification

Reflashed three real ESP32-S3 8MB boards (Waveshare AMOLED 1.8")
in-place without erasing flash — NVS data survived because the
partition table is byte-identical to v0.4.3.1 (only bootloader.bin and
the two app images differ). After upgrade, all three boards now show:

  I (401) csi_collector: Early capture node_id=N (before WiFi init, ruvnet#232/ruvnet#390)
  I (1771) csi_collector: node_id=N verified (early capture matches g_nvs_config)
  I (1801) csi_collector: CSI collection initialized (node_id=N, channel=6)

…with N = 1, 2, 3 respectively, and `Active Nodes: 3` in Home Assistant.

## Files changed

  bootloader.bin             18880 bytes (unchanged size, new ELF)
  esp32-csi-node.bin         990480 -> 1088608 bytes (+98 KiB)
  esp32-csi-node-4mb.bin     773760 -> 872368 bytes (+96 KiB)

Partition tables and ota_data_initial.bin are byte-identical between
v0.4.3.1 and v0.6.4, so the partition layout did not change and the
"flash without erase" upgrade path is safe for existing deployments.

## Source

Upstream release: https://github.com/ruvnet/RuView/releases/tag/v0.6.4-esp32
(published 2026-05-07)

Co-Authored-By: claude-flow <ruv@ruv.net>
@ruvnet

ruvnet commented May 17, 2026

Copy link
Copy Markdown
Owner

Closing — this PR touches rust-port/wifi-densepose-rs/, which was renamed to v2/ long ago. The diff cannot apply against current main. The underlying intent (Windows openblas / macOS Rust build / Confidence trit_signal / node_id NVS) may still be valuable as a clean PR against v2/ — happy to re-review if rebased.

@ruvnet ruvnet closed this May 17, 2026
ruvnet added a commit that referenced this pull request May 20, 2026
…l nodes (#679)

release_bins/ was built from v0.4.3.1 and predated the early-capture
node_id fix (PRs #232/#375/#385/#390). Every device flashed from those
binaries emitted node_id=1 regardless of provisioned ID, making
multi-node deployments appear as a single node.

Changes:
- Rebuild all 6 release_bins/ binaries from v0.6.5 source (2026-05-20)
  - esp32-csi-node.bin (8 MB, 1,110,384 bytes)
  - esp32-csi-node-4mb.bin (4 MB, 894,352 bytes)
  - bootloader.bin, partition-table.bin, partition-table-4mb.bin, ota_data_initial.bin
- Add release_bins/version.txt (0.6.5 / git-sha: d72e06fc8)
- README: add Step 0 "Pre-built binaries" flash command with version reference;
  update expected boot output to show early-capture log line
- provision.py: fix write-flash → write_flash (esptool v4.10+ underscore API)

Validated on real hardware (COM7 — ESP32-S3 N16R8, node_id=2):
  I (396) csi_collector: Early capture node_id=2 (before WiFi init, #232/#390)
  I (406) main: ESP32-S3 CSI Node (ADR-018) — v0.6.5 — Node ID: 2

Closes #679
ruvnet added a commit that referenced this pull request May 21, 2026
* fix(firmware): refresh release_bins to v0.6.5 — fixes node_id=1 on all nodes (#679)

release_bins/ was built from v0.4.3.1 and predated the early-capture
node_id fix (PRs #232/#375/#385/#390). Every device flashed from those
binaries emitted node_id=1 regardless of provisioned ID, making
multi-node deployments appear as a single node.

Changes:
- Rebuild all 6 release_bins/ binaries from v0.6.5 source (2026-05-20)
  - esp32-csi-node.bin (8 MB, 1,110,384 bytes)
  - esp32-csi-node-4mb.bin (4 MB, 894,352 bytes)
  - bootloader.bin, partition-table.bin, partition-table-4mb.bin, ota_data_initial.bin
- Add release_bins/version.txt (0.6.5 / git-sha: d72e06fc8)
- README: add Step 0 "Pre-built binaries" flash command with version reference;
  update expected boot output to show early-capture log line
- provision.py: fix write-flash → write_flash (esptool v4.10+ underscore API)

Validated on real hardware (COM7 — ESP32-S3 N16R8, node_id=2):
  I (396) csi_collector: Early capture node_id=2 (before WiFi init, #232/#390)
  I (406) main: ESP32-S3 CSI Node (ADR-018) — v0.6.5 — Node ID: 2

Closes #679

Co-Authored-By: claude-flow <ruv@ruv.net>

* fix(ci): resolve 3 persistent CI failures + add #679 fix-marker guard

Three jobs have been failing on every push to main since the v1→archive/v1
reorganisation and the softprops/action-gh-release permission tightening:

1. Performance Tests — uvicorn src.api.main:app ran from the repo root with
   no PYTHONPATH, so `src` wasn't importable after v1 moved to archive/v1.
   Added working-directory: archive/v1 to the "Start application" step.
   Added continue-on-error: true — tests/performance/locustfile.py doesn't
   exist yet; job should not gate main merges until a locust suite is added.

2. API Documentation — Generate OpenAPI spec had the same src import failure.
   Added working-directory: archive/v1 to the "Generate OpenAPI spec" step.

3. Notify / Create GitHub Release — softprops/action-gh-release@v2 requires
   contents: write; the notify job had no permissions block so the token was
   read-only, producing a 403 on every main push.
   Added permissions: contents: write to the notify job.

Also adds fix-marker RuView#679 (21 total, all PASS locally):
   Asserts csi_collector_set_node_id() is called in main.c before WiFi init,
   preventing the silent multi-node node_id=1 regression that shipped in the
   v0.4.3.1 release_bins and was fixed + validated on COM7 in PR #681.

Co-Authored-By: claude-flow <ruv@ruv.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants